#!/usr/bin/python
# -*- coding: utf-8 -*-

# Simplified BSD License (BSD)
# 
# Copyright (c) 2013, dr.kohlone@gmail.com
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
# 
# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
# 
# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import imaplib
import smtplib
import socket

DEFAULT_MAX_MESSAGE = 50
DEFAULT_FOLDER='INBOX'

## Mailbox errors
class MailClientError(RuntimeError):
    pass

def safe_method():
    def wrap(f):
        def new_function(*args, **kw):            
            try:                
                return f(*args, **kw)            
            except Exception, e:
                raise MailClientError, e     
        return new_function
    return wrap

class MailClient(object):
    def __init__(self, host='localhost', port=None, ssl=False):
        self._mailbox = None
        self._host = host
        self._port = imaplib.IMAP4_PORT
        self._ssl = ssl
        if port:
            self._port = port
        else:
            if ssl:
                self._port = imaplib.IMAP4_SSL_PORT
    
    @safe_method()        
    def open(self, user, password):
        if(self._ssl):
            self._mailbox = imaplib.IMAP4_SSL(self._host, self._port)
        else:
            self._mailbox = imaplib.IMAP4(self._host, self._port)
        
        #TODO: check login capabilities !
        self._mailbox.login(user,password)
        
    def close(self):
        try:
            self._mailbox.close()
            self._mailbox.logout()
        except:
            pass
        finally:
            self._mailbox = None
    
    @safe_method()
    def get_message_by_id(self, message_uid):
        res, mails = self._mailbox.uid("FETCH", message_uid, '(RFC822)')
        if res in ['OK']:
            for mail in mails:
                if isinstance(mail, tuple):
                    return mail[1]
        return None
    
    @safe_method()       
    def _get_messages(self, request, max_message, header_only):
        result = []
        list_message = self._mailbox.search(None, request)[1][0].split()
        s_list_message = ",".join(list_message[0:max_message])
        if (s_list_message):
            if header_only:
                mails = self._mailbox.fetch(s_list_message, '(UID RFC822.HEADER)')[1]
                #mails = self._mailbox.fetch(s_list_message, '(UID)')[1]
            else:
                mails = self._mailbox.fetch(s_list_message, '(UID RFC822)')[1]
            for mail in mails:
                if isinstance(mail, tuple):
                    message_uid = mail[0].split()[2]
                    result.append((message_uid, mail[1]))
        return len(result), result
            
    def get_messages(self, max_message=DEFAULT_MAX_MESSAGE, header_only=False):
        return self._get_messages("(UNDELETED)", max_message, header_only)
    
    def get_unanswered_messages(self, max_message=DEFAULT_MAX_MESSAGE, header_only=False):
        return self._get_messages("(UNANSWERED)", max_message, header_only)
    
    @safe_method()
    def select(self, folder=DEFAULT_FOLDER):
        self._mailbox.select(mailbox=folder)
    
    @safe_method()
    def mark_message_as_deleted(self, message_uid):
        self._mailbox.uid("STORE", message_uid, '+FLAGS', '(\Deleted)')
    
    @safe_method()   
    def mark_message_as_answered(self, message_uid):
        self._mailbox.uid("STORE", message_uid, '+FLAGS', '(\Answered)')
    
    @safe_method()     
    def check(self) :
        self._mailbox.check()
    
    @safe_method()
    def expunge_messages(self):
        self._mailbox.expunge()
    
    @safe_method()     
    def append_message(self, folder, message):
        self._mailbox.append(folder, None, None, message)
    
    @safe_method()
    def create_folder(self, folder_name):
        ok, box_list = self._mailbox.lsub()
        if ok:
            check_box = '"%s"' % (folder_name.lower())
            test_list = [True for box in box_list if box.lower().endswith(check_box)]
            if not test_list:
                self._mailbox.create(folder_name)
                
            self._mailbox.subscribe(folder_name)
                        
    @safe_method()    
    def copy_message_to_folder(self, message_uid, folder):
        self._mailbox.uid("COPY", message_uid, folder)
        
    def __del__ (self):
        # TODO: is expunge message really needed ?
        try:
            self.expunge_messages()
        except:
            pass
        try:
            self.close()
        except:
            pass

class SMTPConnectError(StandardError):
    pass     

class SMTPClient(object):
    def __init__ (self, host, port=0, tls=False, time_out=30):
        # connection timeout 
        socket.setdefaulttimeout(time_out)
        self.host = host
        self.port = port
        self.tls = tls
        self.smtp = None
        
    def connect(self, user=None, password=None):
        try:
            self.smtp = smtplib.SMTP(self.host, self.port)
            self.smtp.ehlo()
            if self.tls:
                self.smtp.starttls()
                self.smtp.ehlo()   
            if user:
                self.smtp.login(user, password)
        except Exception, err:
            raise SMTPConnectError('Connection error: %s' % str(err))
        
    def close(self):
        try:
            self.smtp.quit()
        except:
            pass
        
    def send(self, msg, from_addr, to_addrs, mail_options=None, rcpt_options=None):
        if not mail_options:
            mail_options = []
        if not rcpt_options:
            rcpt_options = []
        try:
            self.smtp.sendmail(from_addr, to_addrs, msg, mail_options, rcpt_options)
        except Exception, err:
            raise SMTPConnectError('Send error: %s' % str(err))
            